/*
 * Protocol.java
 *
 * Created on 13 Май 2008 г., 12:08
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.jimm.protocol;


import java.util.Vector;

import org.jimm.app.ContactListActivity;
import org.jimm.app.JimmApplication;
import org.jimm.app.JimmException;
import org.jimm.app.R;
import org.jimm.comm.*;
import org.jimm.db.JimmDatabase.Accounts;
import org.jimm.message.Message;
import org.jimm.message.PlainMessage;
import org.jimm.modules.*;
import org.jimm.service.Preferences;

import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.View;

/**
 * @author vladimir, r.u.s.h.a.n.
 */
abstract public class Protocol extends TreeBranch {
    private static final String LOG_TAG = "Jimm:protocol.Protocol";
    protected Preferences p;
    protected Vector<Contact> contacts = new Vector<Contact>();
    protected Vector<Group> groups = new Vector<Group>();
    private Profile profile;
    private String password;
    private String userid = "";
    protected StatusInfo info;

    protected XStatusInfo xstatusInfo;

    private boolean isReconnect;
    private int reconnect_attempts;

    private long lastStatusChangeTime;
    private int progress = 100;
    private int privateStatus = 0;

    private final Object rosterLockObject = new Object();
    private Vector<TreeNode> sortedContacts = new Vector<TreeNode>();
    private Vector<TreeNode> sortedGroups = new Vector<TreeNode>();
    private Group notInListGroup;
    public ProtocolListener listener;
    private View convertView;


    public void setProtocolListener(ProtocolListener l) {
        listener = l;
    }

    public void setConvertView(View convertView) {
        this.convertView = convertView;

    }

    public View getConvertView() {
        return convertView;
    }

    public final ContextMenu getContextMenu(ContextMenu menu, ContactListActivity a) {
        //if (p.isConnected()) {
        //return new ManageContactListForm(p, this).getMenu();
        //}
        return null;
    }

    public abstract String getUserIdName();

    public final String getUserId() {
        return userid;
    }

    protected final void setUserId(String userId) {
        userid = userId;
    }

    public boolean isEmpty() {
        if (p.hideOfflineContacts) {
            Vector<Contact> contacts = getContactItems();
            for (int i = contacts.size() - 1; 0 <= i; --i) {
                if (((Contact) contacts.elementAt(i)).isVisibleInContactList()) {
                    return false;
                }
            }
            return true;
        }
        return (0 == getContactItems().size()) && (0 == getGroupItems().size());
    }

    public final String getNick() {
        String nick = profile.nick;
        return (nick.length() == 0) ? JimmApplication.getContext().getString(R.string.me) : nick;
    }

    public String getText() {
        return getNick() + " (" + getOnlineContactsCount() + "/" + getContactItems().size() + ")";
    }

    public final Profile getProfile() {
        return profile;
    }

    public final int getCount() {
        if (p.showUserGroups) {
            if (p.hideEmptyGroups) {
                int onlineCount = 0;
                int total = getGroupItems().size();
                for (int i = 0; i < total; ++i) {
                    Group item = getGroupItems().elementAt(i);
                    if (item.getOnlineCount() > 0) {
                        onlineCount++;
                    }
                }
                return onlineCount;
            } else {
                return getGroupItems().size();
            }
        } else {
            if (p.hideOfflineContacts) {
                int onlineCount = 0;
                int total = getContactItems().size();
                for (int i = 0; i < total; ++i) {
                    Contact item = getContactItems().elementAt(i);
                    if (item.isVisibleInContactList()) {
                        onlineCount++;
                    }
                }
                return onlineCount;
            } else {
                return getContactItems().size();
            }

        }
    }

    public final String getPassword() {
        return (null == password) ? profile.password : password;
    }

    public final void setPassword(String pass) {
        password = pass;
    }

    private String getDefaultDomain(int type) {
        switch (type) {
            case Accounts.PROTOCOL_GTALK:
                return "@gmail.com";
            case Accounts.PROTOCOL_FACEBOOK:
                return "@chat.facebook.com";
            case Accounts.PROTOCOL_LJ:
                return "@livejournal.com";
            case Accounts.PROTOCOL_YANDEX:
                return "@ya.ru";
            case Accounts.PROTOCOL_VK:
                return "@vk.com";
            case Accounts.PROTOCOL_QIP:
                return "@qip.ru";
            case Accounts.PROTOCOL_ODNOKLASSNIKI:
                return "@odnoklassniki.ru";
        }
        return null;
    }

    public final void setProfile(Profile account) {
        this.profile = account;
        String rawUin = StringConvertor.notNull(account.userId);

        if (!TextUtils.isEmpty(rawUin)) {
            int type = account.protocolType;
            if ((Accounts.PROTOCOL_VK == type)
                    && Character.isDigit(rawUin.charAt(0))) {
                rawUin = "id" + rawUin;
                account.userId = rawUin;
            }
            String domain = getDefaultDomain(type);
            if ((null != domain) && (-1 == rawUin.indexOf('@'))) {
                rawUin += domain;
            }
        }
        userid = TextUtils.isEmpty(rawUin) ? "" : processUin(rawUin);
        if (!TextUtils.isEmpty(account.password)) {
            setPassword(null);
        }
    }

    protected String processUin(String uin) {
        return uin;
    }

    public final XStatusInfo getXStatusInfo() {
        return xstatusInfo;
    }

    public final void init(Preferences p) {
        this.p = p;

        // Not In List Group
        notInListGroup = new Group(JimmApplication.getContext().getString(R.string.group_not_in_list));
        notInListGroup.setMode(Group.MODE_NONE);
        notInListGroup.setGroupId(Group.NOT_IN_GROUP);

        // Status info
        initStatusInfo();

        // Status
        initStatus();
    }

    protected abstract void initStatusInfo();

    public final int getCurrentStatusIcon() {
        if (isConnected() && !isConnecting()) {
            return getStatusInfo().getIcon(getProfile().statusIndex);

        } else {
            if (isConnecting())
                return getStatusInfo().getIcon(StatusInfo.STATUS_CONNECTING);
        }

        return getStatusInfo().getIcon(StatusInfo.STATUS_OFFLINE);
    }

    public final void sort() {
        synchronized (rosterLockObject) {
            if (p.showUserGroups) {
                Util.sort(getSortedGroups());
            } else {
                Util.sort(getSortedContacts());
            }
        }
    }

    public final void setContactListStub() {
        synchronized (rosterLockObject) {
            contacts = new Vector<Contact>();
            groups = new Vector<Group>();
        }
    }

    public final void setContactList(Vector<Group> groups, Vector<Contact> contacts) {
        synchronized (rosterLockObject) {
            this.contacts = contacts;
            this.groups = groups;
        }
//        ChatHistory.instance.restoreContactsWithChat(this);

        synchronized (rosterLockObject) {
            sortedContacts = new Vector<TreeNode>();
            for (int i = 0; i < contacts.size(); ++i) {
                sortedContacts.addElement(contacts.elementAt(i));
            }
            sortedGroups = new Vector<TreeNode>();
            for (int i = 0; i < groups.size(); ++i) {
                Group g = (Group) groups.elementAt(i);
                updateContacts(g);
                sortedGroups.addElement(g);
            }
            Util.sort(sortedGroups);
            updateContacts(notInListGroup);
        }
//        getContactList().getManager().update();
        needSave();
    }

    // #sijapp cond.if protocols_JABBER is "true" #
    public final void setContactListAddition(Group group) {
        synchronized (rosterLockObject) {
            updateContacts(group);

            updateContacts(notInListGroup);
            Vector<Contact> groupItems = group.getContacts();
            for (int i = 0; i < groupItems.size(); ++i) {
                if (-1 == Util.getIndex(sortedContacts, groupItems.elementAt(i))) {
                    sortedContacts.addElement(groupItems.elementAt(i));
                }
            }
        }
//        getContactList().getManager().update();
        needSave();
    }

    // #sijapp cond.end#
    private void updateContacts(Group group) {
        Vector<Contact> allItems = getContactItems();
        Vector<Contact> groupItems = group.getContacts();
        groupItems.removeAllElements();
        int size = allItems.size();
        int groupId = group.getId();
        for (int i = 0; i < size; ++i) {
            Contact item = (Contact) allItems.elementAt(i);
            if (item.getGroupId() == groupId) {
                groupItems.addElement(item);
            }
        }
        group.updateGroupData();
        group.sort();
    }

    /* ********************************************************************* */
    public final void setConnectingProgress(int percent) {
        if (100 == percent) {
            setConnected();
        }
//        if (percent < 0) {
//            percent = 100;
//        }
        this.progress = percent;

        if (-1 != progress)
            if(listener != null)    // todo listener == null if add first account without reboot
            listener.onUpdateConnectingProgress(this, percent);
//        getContactList().getManager().invalidate();
    }

    public final boolean isConnecting() {
        return 100 != progress && -1 != progress;
    }

    public final int getConnectingProgress() {
        return progress;
    }

    /* ********************************************************************* */
    public abstract void getAvatar(UserInfo userInfo);

    /* ********************************************************************* */
    protected abstract void requestAuth(String userId);

    protected abstract void grandAuth(String userId);

    protected abstract void denyAuth(String userId);

    protected abstract void s_setPrivateStatus();

    public final void setPrivateStatus(int status) {
        privateStatus = status;
        if (isConnected()) {
            s_setPrivateStatus();
        }
    }

    public final int getPrivateStatus() {
        return privateStatus;
    }

    public final void requestAuth(Contact contact) {
        requestAuth(contact.getUserId());
    }
    /* ********************************************************************* */

    //    private boolean needSave = false;
    public final void needSave() {
//       needSave = true;
//        ContactList.getInstance().needRosterSave();
    }


    /* ********************************************************************* */

    protected void s_removeContact(Contact contact) {
    }

    ;

    protected void s_removedContact(Contact contact) {
    }

    ;

    public final void removeContact(Contact contact) {
        // Check whether contact item is temporary
        if (contact.isTemp()) {
        } else if (isConnected()) {
            // Request contact item removal
            s_removeContact(contact);
        } else {
            return;
        }
        removeLocalContact(contact);
    }

    abstract protected void s_renameContact(Contact contact, String name);

    public final void renameContact(Contact contact, String name) {
        if (TextUtils.isEmpty(name)) {
            return;
        }
        if (!inContactList(contact)) {
            contact.setName(name);
            return;
        }
        if (contact.isTemp()) {
        } else if (isConnected()) {
            s_renameContact(contact, name);
        } else {
            return;
        }
        contact.setName(name);
        cl_renameContact(contact);
        needSave();
    }

    abstract protected void s_moveContact(Contact contact, Group to);

    public final void moveContactTo(Contact contact, Group to) {
        s_moveContact(contact, to);
        cl_moveContact(contact, to);
    }

    protected void s_addContact(Contact contact) {
    }

    ;

    protected void s_addedContact(Contact contact) {
    }

    public final void addContact(Contact contact) {
        s_addContact(contact);
        contact.setTempFlag(false);
        cl_addContact(contact);
//        getContactList().getManager().setActiveContact(contact);
        needSave();
        s_addedContact(contact);
    }

    public final void addTempContact(Contact contact) {
        cl_addContact(contact);
    }

    abstract protected void s_removeGroup(Group group);

    public final void removeGroup(Group group) {
        s_removeGroup(group);
        cl_removeGroup(group);
        needSave();
    }

    abstract protected void s_renameGroup(Group group, String name);

    public final void renameGroup(Group group, String name) {
        s_renameGroup(group, name);
        group.setName(name);
        cl_renameGroup(group);
        needSave();
    }

    abstract protected void s_addGroup(Group group);

    public final void addGroup(Group group) {
        s_addGroup(group);
        cl_addGroup(group);
        needSave();
    }

    abstract public boolean isConnected();

    abstract protected void startConnection();

    abstract protected void closeConnection();

    protected void userCloseConnection() {
    }

    private void disconnect(boolean user) {
        setConnectingProgress(-1);
        closeConnection();
        if (user) {
            userCloseConnection();
        }
        /* Reset all contacts offline */
        setStatusesOffline();
        /* Disconnect */
        // #sijapp cond.if modules_TRAFFIC is "true" #
//        Traffic.getInstance().safeSave();
        // #sijapp cond.end#
//        getContactList().getManager().update();
        listener.onDisconnected(this);
    }

    public final void disconnect() {
        disconnect(true);
    }


    abstract public Group createGroup(String name);

    public abstract Contact createContact(String uin, String name);

    public final Contact createTempContact(String uin, String name) {
        Contact contact = getItemByUIN(uin);
        if (null != contact) {
            return contact;
        }
        contact = createContact(uin, name);
        if (null != contact) {
            contact.setTempFlag(true);
        }
        return contact;
    }

    public final Contact createTempContact(String uin) {
        return createTempContact(uin, uin);
    }

//    abstract protected void s_searchUsers(Search cont);
//    public final void searchUsers(Search cont) {
//        s_searchUsers(cont);
//    }
//    public final Search getSearchForm() {
//        if (groups.isEmpty()) {
//            return null;
//        }
//        return new Search(this);
//    }

    public final Vector<Contact> getContactItems() {
        return contacts;
    }

    public final Vector<Group> getGroupItems() {
        return groups;
    }

    public final String[] getGroups() {
        int j = 0;
        int n = getGroupItems().size();
        String[] _groups = new String[n];

        for (int i = 0; i < n; i++) {
            _groups[j] = groups.get(i).getName();
            j++;
        }

        return _groups;
    }

    // #sijapp cond.if modules_SOUND is "true" #
    public final void beginTyping(String uin, boolean type) {
        Contact item = getItemByUIN(uin);
        if ((null == item) && type) {
            // #sijapp cond.if modules_ANTISPAM is "true" #
            if (p.antispamEnable) {
                return;
            }
            // #sijapp cond.end #
            item = createTempContact(uin);
            addTempContact(item);
        }
        beginTyping(item, type);
    }

    private final void beginTyping(Contact c, boolean type) {
        if (null == c) {
            return;
        }
        if (c.isTyping() != type) {
            c.beginTyping(type);
            updateStatus(c);
            if (c.hasChat()) {
//                getChat(item).beginTyping(type);
            }
            if (type && isConnected()) {
                JimmApplication.getInstance().getNotifications().onTyping();
            }
//            getContactList().getManager().invalidate();
            listener.onUpdateContacList(this, c);
        }
    }
    // #sijapp cond.end #

    public final int getUnreadMessageCount() {
        int n = contacts.size();
        int count = 0;
        for (int i = 0; i < n; i++) {
            count += contacts.elementAt(i).getUnreadMessageCount();
        }
        return count;
    }

    private void updateStatus(Contact c) {
//        ChatTextList chat = ChatHistory.instance.getChat(c);
//        if ((null != chat) && (chat == Jimm.getJimm().getDisplay().getCurrentDisplay())) {
//            chat.updateStatusIcons();
//        }
    }

    protected final void setStatusesOffline() {
        for (int i = contacts.size() - 1; i >= 0; --i) {
            Contact c = (Contact) contacts.elementAt(i);
            c.setOfflineStatus();
        }
        synchronized (rosterLockObject) {
            if (p.showUserGroups) {
                for (int i = groups.size() - 1; i >= 0; --i) {
                    ((Group) groups.elementAt(i)).updateGroupData();
                }
            }
        }
    }

    public final int getOnlineContactsCount() {
        int onlineCount = 0;
        int total = contacts.size();
        for (int i = 0; i < total; ++i) {
            Contact item = contacts.elementAt(i);
            if (item.isOnline()) {
                onlineCount++;
            }
        }
        return onlineCount;
    }

    public final Contact getItemByUIN(String uin) {
        for (int i = contacts.size() - 1; i >= 0; --i) {
            Contact contact = (Contact) contacts.elementAt(i);
            if (contact.getUserId().equals(uin)) {
                return contact;
            }
        }
        return null;
    }

    public final Group getGroupById(int id) {
        synchronized (rosterLockObject) {
            for (int i = groups.size() - 1; 0 <= i; --i) {
                Group group = (Group) groups.elementAt(i);
                if (group.getId() == id) {
                    return group;
                }
            }
        }
        return null;
    }

    public final Group getGroup(Contact contact) {
        return getGroupById(contact.getGroupId());
    }

    public final Group getGroup(String name) {
        synchronized (rosterLockObject) {
            for (int i = groups.size() - 1; 0 <= i; --i) {
                Group group = (Group) groups.elementAt(i);
                if (group.getName().equals(name)) {
                    return group;
                }
            }
        }
        return null;
    }

    public final Group getNotInListGroup() {
        return notInListGroup;
    }

    public final boolean inContactList(Contact contact) {
        return -1 != Util.getIndex(contacts, contact);
    }

    public final StatusInfo getStatusInfo() {
        return info;
    }

    protected abstract void s_updateOnlineStatus();

    public final void setOnlineStatus(int statusIndex, String msg) {
        getProfile().statusIndex = (byte) statusIndex;
        getProfile().statusMessage = msg;

        setLastStatusChangeTime();
        if (isConnected()) {
            s_updateOnlineStatus();
        }
    }

    protected abstract void s_updateXStatus();

    public final void setXStatus(int xstatus, String title, String desc) {
        p.xstatusIndex = xstatus;
        p.xstatusTitles[xstatus] = title;
        p.xstatusDescriptions[xstatus] = desc;
        p.save();
        if (isConnected()) {
            s_updateXStatus();
        }
    }

    private final void initStatus() {
        setLastStatusChangeTime();
        // #sijapp cond.if modules_SERVERLISTS is "true" #
        setPrivateStatus((byte) p.privateStatus);
        // #sijapp cond.end #
    }

    ///////////////////////////////////////////////////////////////////////////
    private void ui_removeFromAnyGroup(Contact c) {
        Vector<Group> allGroups = getGroupItems();
        for (int i = 0; i < allGroups.size(); ++i) {
            Group group = (Group) allGroups.elementAt(i);
            if (group.removeContact(c)) {
                group.updateGroupData();
                return;
            }
        }
        notInListGroup.removeContact(c);
        notInListGroup.updateGroupData();
    }

    private void ui_addContactToGroup(Contact contact, Group group) {
        ui_removeFromAnyGroup(contact);
        contact.setGroup(group);
        if (null != group) {
            group.addContact(contact);
        } else {
            notInListGroup.addContact(contact);
        }
    }

    private void ui_updateContactInGroup(Contact contact, Group group) {
        // TODO: sort contact only if group is expanded or when group is going to be expanded
        synchronized (rosterLockObject) {
            if (null == group) {
                group = notInListGroup;
            }
            //getContactList().getManager().putIntoQueue(group);
        }
    }

    private final void ui_updateGroup(Group group) {
        if (p.showUserGroups) {
            synchronized (rosterLockObject) {
                group.updateGroupData();
                Util.sort(sortedGroups);
            }
            ui_updateCL(group);
        }
    }

    private void ui_updateCL(Contact c) {
        listener.onUpdateContacList(this, c);

        // getContactList().getManager().update(c);

    }

    private void ui_updateCL(Group g) {
        listener.onUpdateContacList(this, g);

        //getContactList().getManager().update(g);

    }

    ///////////////////////////////////////////////////////////////////////////
    public final Vector<TreeNode> getSortedContacts() {
        return sortedContacts;
    }

    public final Vector<TreeNode> getSortedGroups() {
        return sortedGroups;
    }

    public final Object getRosterLockObject() {
        return rosterLockObject;
    }

    ///////////////////////////////////////////////////////////////////////////
    public final void markMessages(Contact contact) {
        if (p.sortUpWithMsg) {
            ui_updateContact(contact);
        }
//        getContactList().markMessages(contact);
    }

    public final void ui_changeContactStatus(Contact contact) {
        updateStatus(contact);
        ui_updateContact(contact);
    }

    public final void ui_updateContact(Contact contact) {
        ui_updateContactInGroup(contact, getGroup(contact));
        ui_updateCL(contact);
    }

    private void cl_addContact(Contact contact) {
        if (null == contact) {
            return;
        }
        Group g = getGroup(contact);
        boolean hasnt = !inContactList(contact);
        if (hasnt) {
            contacts.addElement(contact);
        }
        synchronized (rosterLockObject) {
            if (hasnt) {
                sortedContacts.addElement(contact);
            }
            ui_addContactToGroup(contact, g);
        }
        ui_updateContact(contact);
    }

    private void cl_renameContact(Contact contact) {
        ui_updateContact(contact);
    }

    private void cl_moveContact(Contact contact, Group to) {
        synchronized (rosterLockObject) {
            ui_addContactToGroup(contact, to);
        }
        ui_updateContact(contact);
    }

    private void cl_removeContact(Contact contact) {
        contacts.removeElement(contact);
        synchronized (rosterLockObject) {
            sortedContacts.removeElement(contact);
            ui_removeFromAnyGroup(contact);
        }
        ui_updateCL(contact);
    }

    private void cl_addGroup(Group group) {
        // #sijapp cond.if modules_DEBUGLOG is "true" #
        //if (-1 != Util.getIndex(groups, group)) {
        //    DebugLog.panic("Group '" + group.getName() + "' already added");
        //}
        // #sijapp cond.end #
        groups.addElement(group);
        synchronized (rosterLockObject) {
            sortedGroups.addElement(group);
            ui_updateGroup(group);
        }
    }

    private void cl_renameGroup(Group group) {
        ui_updateGroup(group);
    }

    private void cl_removeGroup(Group group) {
        groups.removeElement(group);
        synchronized (rosterLockObject) {
            sortedGroups.removeElement(group);
        }
        ui_updateCL(group);
    }

    public final void addLocalContact(Contact contact) {
        cl_addContact(contact);
    }

    public final void removeLocalContact(Contact contact) {
        if (null == contact) {
            return;
        }
        boolean inCL = inContactList(contact);
        if (inCL) {
            cl_removeContact(contact);
        }
        if (contact.hasChat()) {
//            ChatHistory.instance.unregisterChat(ChatHistory.instance.getChat(contact));
        }
        if (inCL) {
            if (isConnected()) {
                s_removedContact(contact);
            }
            if (!contact.isTemp()) {
                needSave();
            }
        }
    }
    ///////////////////////////////////////////////////////////////////////////

    public final long getLastStatusChangeTime() {
        return lastStatusChangeTime;
    }

    private void setLastStatusChangeTime() {
        lastStatusChangeTime = System.currentTimeMillis() / 1000;
    }

    private boolean isEmptyMessage(String text) {
        for (int i = 0; i < text.length(); ++i) {
            if (' ' < text.charAt(i)) {
                return false;
            }
        }
        return true;
    }

    public final void addMessage(Message message) {
        addMessage(message, false);
    }

    public final void addMessage(Message message, boolean silent) {

        Contact contact = (Contact) getItemByUIN(message.getSndrUin());
        // #sijapp cond.if modules_ANTISPAM is "true" #
        if ((null == contact) && AntiSpam.isSpam(this, message)) {
            return;
        }
        // #sijapp cond.end #

        // Add message to contact
        if (null == contact) {
            // Create a temporary contact entry if no contact entry could be found
            // do we have a new temp contact
            contact = createTempContact(message.getSndrUin());
            addTempContact(contact);
        }
        if (null == contact) {
            return;
        }

        // #sijapp cond.if modules_SERVERLISTS is "true" #
        if (contact.inIgnoreList()) {
            return;
        }
        // #sijapp cond.end #
        // #sijapp cond.if modules_SOUND is "true" #
        beginTyping(contact, false);
        // #sijapp cond.end #
        boolean isPlain = (message instanceof PlainMessage);
        if (isPlain && isEmptyMessage(message.getText())) {
            return;
        }

        // Adds a message to the message display
        message.isReaded = false;
        listener.onMessage(this, contact, (PlainMessage) message);

//        getChat(contact).addMessage(message, !silent && !message.isWakeUp());
        if (!silent) {
            addMessageNotify(contact, message);
            if (p.sortUpWithMsg) {
                ui_updateContact(contact);
            }
        }
        //ContactList.getInstance().recivedMessage(contact);
    }

    private void addMessageNotify(Contact contact, Message message) {
//        final boolean isPlainMsg = (message instanceof PlainMessage);
        final boolean isOnlineMsg = !message.isOffline();

        boolean isSingleUser = contact.isSingleUserContact();
        boolean isMultiUserNotify = false;
        // #sijapp cond.if protocols_JABBER is "true" #
        if (!isSingleUser && isOnlineMsg) {
            String msg = message.getText();
            String myName = contact.getMyName();
            // regexp: "^nick. "
            isSingleUser = msg.startsWith(myName)
                    && msg.startsWith(" ", myName.length() + 1);
//            isMultiUserNotify = ChatTextList.isHighlight(msg, myName);
        }
        // #sijapp cond.end #


        // #sijapp cond.if modules_LIGHT is "true" #
        if (isSingleUser || isMultiUserNotify) {
//            CustomLight.setLightMode(CustomLight.ACTION_MESSAGE);
        }
        // #sijapp cond.end#

        // #sijapp cond.if modules_SOUND is "true" #
        if (message.isOffline()) {
            // Offline messages don't play sound

        } else if (isSingleUser) {
            if (contact.isSingleUserContact()
                    && contact.isAuth() && !contact.isTemp()
                    && message.isWakeUp()) {
//                playNotification(Notify.NOTIFY_ALARM);

            } else if (isBlogBot(contact.getUserId())) {
//                playNotification(Notify.NOTIFY_BLOG);

            } else {
                JimmApplication.getInstance().getNotifications().onInputMessage();
            }

            // #sijapp cond.if protocols_JABBER is "true" #
        } else if (isMultiUserNotify) {
//            playNotification(Notify.NOTIFY_MULTIMESSAGE);
            // #sijapp cond.end #
        }
        // #sijapp cond.end#
    }

    protected boolean isBlogBot(String userId) {
        return false;
    }

    public final void setAuthResult(String uin, boolean auth) {
        Contact c = getItemByUIN(uin);
        if (null == c) {
            return;
        }
        if (auth == c.isAuth()) {
            return;
        }
        c.setBooleanValue(Contact.CONTACT_NO_AUTH, !auth);
        if (!auth) {
            c.setOfflineStatus();
        }
        ui_changeContactStatus(c);
    }


    public final void connect() {
        isReconnect = false;
        reconnect_attempts = p.reconnectNumber;

        startConnection();
        setLastStatusChangeTime();
    }

    // Puts the comm. subsystem into STATE_CONNECTED
    private final void setConnected() {
        reconnect_attempts = p.reconnectNumber;
    }

    public final boolean isReconnect() {
        return isReconnect;
    }

    public final void processException(JimmException e) {
        // #sijapp cond.if modules_DEBUGLOG is "true" #
//        DebugLog.println("process exception: " + e.getMessage());
        // #sijapp cond.end #
        if (e.isReconnectable()) {
            reconnect_attempts--;
            if (0 < reconnect_attempts) {
                if (isConnected() && !isConnecting()) {
                    isReconnect = true;
                }
                try {
                    int iter = p.reconnectNumber - reconnect_attempts;
                    int sleep = Math.min(iter * 10, 2 * 60);
                    Thread.sleep(sleep * 1000);
                } catch (Exception ex) {
                }
                if (isConnected() || isConnecting()) {
                    disconnect(false);
                    JimmApplication.getInstance().getNotifications().onReconnect();
                    startConnection();
                }
                return;
            }
        }
        // Critical exception
        if (e.isCritical()) {
            disconnect(false);
        }
//        Jimm.unlockJimm();
//        getContactList().activate();
//        String message = getUserId() + "\n" + e.getMessage();
//        new Popup(message).show();
    }

    /**
     * Release all resources used by the protocol.
     */
    public final void dismiss() {
        disconnect();
//        ChatHistory.instance.unregisterChats(this);
//        safeSave();
        sortedContacts = null;
        sortedGroups = null;
        profile = null;
        contacts = null;
        groups = null;
    }

    public void autoDenyAuth(String uin) {
    }

    public abstract void saveUserInfo(UserInfo info);

    public abstract int[] getStatusList();

    public boolean isMeVisible(Contact to) {
        return true;
    }

    protected abstract void s_sendTypingNotify(Contact to, boolean isTyping);

    public final void sendTypingNotify(Contact to, boolean isTyping) {
        if (isConnected() && isMeVisible(to) && p.typingMode) {
            s_sendTypingNotify(to, isTyping);
        }
    }

    protected abstract void sendSomeMessage(PlainMessage msg);

    public final void sendMessage(Contact to, String msg, boolean addToChat) {
        msg = StringConvertor.trim(msg);
        if (TextUtils.isEmpty(msg) || !isConnected()) {
            return;
        }
        Log.i(LOG_TAG, "sendMessage: to = " + to.getName());

        // #sijapp cond.if protocols_JABBER is "true" #
//        if (msg.startsWith("/") && !msg.startsWith("/me ") && (to instanceof JabberContact)) {
//    	    boolean cmdExecuted = ((JabberContact)to).execCommand(this, msg);
//    	    if (cmdExecuted) {
//                return;
//    	    }
//        }
        // #sijapp cond.end #

        PlainMessage plainMsg = new PlainMessage(this, to, System.currentTimeMillis(), msg);
        sendSomeMessage(plainMsg);
        if (addToChat) {
//            getChat(to).addMyMessage(plainMsg);
        }
    }

    protected void doAction(Contact contact, int cmd) {
    }

    public abstract void showUserInfo(Contact contact, Activity activity);

    public abstract void showStatus(Contact contact);

    public final void setContactStatus(Contact contact, int status, String text) {
        int prev = contact.getStatusIndex();
        contact.setStatus(status, text);
        if (isConnected() && !isConnecting()) {
            int curr = contact.getStatusIndex();
            if ((prev == curr) || !contact.isSingleUserContact()) {
                return;
            }
            // #sijapp cond.if modules_SOUND is "true" #
            if (!getStatusInfo().isAway(curr) && getStatusInfo().isAway(prev)) {
                JimmApplication.getInstance().getNotifications().onContactOnline();
            }
            // #sijapp cond.end #
//            contact.showTopLine(this, getStatusInfo().getName(curr));
        }
    }

    public String getUniqueUserId(Contact contact) {
        return contact.getUserId();
    }
//    public final ChatTextList getChat(Contact contact) {
//        ChatTextList chat = ChatHistory.instance.getChat(contact);
//        if (null == chat) {
//            chat = new ChatTextList(this, contact);
//            if (!inContactList(contact)) {
//                contact.setTempFlag(true);
//                addLocalContact(contact);
//            }
//        }
//        return chat;
//    }
}
